#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinter.scrolledtext import ScrolledText
import threading
from typing import List, Dict, Callable, Optional, Tuple, Any

from compression import CompressionHandler
from utils import Utils

class DragDropListbox(tk.Listbox):
    """支持拖放操作的Listbox"""
    
    def __init__(self, master=None, **kw):
        super().__init__(master, **kw)
        self.bind('<ButtonPress-1>', self.on_button_press)
        self.bind('<B1-Motion>', self.on_motion)
        self.bind('<ButtonRelease-1>', self.on_button_release)
        self.drag_start_index = None
    
    def on_button_press(self, event):
        """鼠标按下事件处理"""
        # 获取当前点击的项目索引
        index = self.nearest(event.y)
        if index >= 0:
            self.drag_start_index = index
            # 保存当前选中项
            self.selection_clear(0, tk.END)
            self.selection_set(index)
    
    def on_motion(self, event):
        """鼠标移动事件处理"""
        if self.drag_start_index is not None:
            # 获取当前鼠标位置对应的索引
            index = self.nearest(event.y)
            if index >= 0 and index != self.drag_start_index:
                # 获取拖动项的值
                value = self.get(self.drag_start_index)
                # 删除原项
                self.delete(self.drag_start_index)
                # 在新位置插入
                self.insert(index, value)
                # 更新拖动起始索引
                self.drag_start_index = index
                # 保持选中状态
                self.selection_clear(0, tk.END)
                self.selection_set(index)
    
    def on_button_release(self, event):
        """鼠标释放事件处理"""
        self.drag_start_index = None


class FileDropTarget:
    """文件拖放目标区域"""
    
    def __init__(self, widget, on_drop_callback):
        """
        初始化拖放目标
        
        Args:
            widget: 要接收拖放的控件
            on_drop_callback: 拖放回调函数，接收文件路径列表
        """
        self.widget = widget
        self.on_drop_callback = on_drop_callback
        
        # 绑定拖放事件
        try:
            # 尝试使用TkDnD
            self.widget.drop_target_register("DND_Files")
            self.widget.dnd_bind('<<Drop>>', self.on_drop)
        except (tk.TclError, AttributeError):
            # 如果TkDnD不可用，显示提示
            print("TkDnD not available, drag and drop functionality disabled")
    
    def on_drop(self, event):
        """处理拖放事件"""
        # 获取拖放的文件路径
        data = event.data
        
        # 根据操作系统处理路径格式
        if os.name == 'nt':  # Windows
            files = data.split('} {')
            if len(files) == 1:
                files = data.split()
            # 移除可能的大括号
            files = [f.strip('{}') for f in files]
        else:  # macOS, Linux
            files = data.split()
        
        # 调用回调函数
        if self.on_drop_callback:
            self.on_drop_callback(files)


class ProgressDialog(tk.Toplevel):
    """进度对话框"""
    
    def __init__(self, parent, title="处理中", message="正在处理，请稍候..."):
        """
        初始化进度对话框
        
        Args:
            parent: 父窗口
            title: 对话框标题
            message: 显示消息
        """
        super().__init__(parent)
        self.title(title)
        self.resizable(False, False)
        self.transient(parent)
        self.grab_set()
        
        # 窗口大小和位置
        window_width = 400
        window_height = 150
        
        # 获取屏幕尺寸
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        
        # 计算居中位置
        x = (screen_width - window_width) // 2
        y = (screen_height - window_height) // 2
        
        # 设置窗口位置
        self.geometry(f"{window_width}x{window_height}+{x}+{y}")
        
        # 创建控件
        self.frame = ttk.Frame(self, padding=20)
        self.frame.pack(fill=tk.BOTH, expand=True)
        
        # 消息标签
        self.message_label = ttk.Label(self.frame, text=message)
        self.message_label.pack(pady=(0, 10))
        
        # 进度条
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(
            self.frame, 
            variable=self.progress_var, 
            length=350, 
            mode='determinate'
        )
        self.progress_bar.pack(pady=5)
        
        # 进度文本
        self.progress_text = ttk.Label(self.frame, text="0%")
        self.progress_text.pack(pady=5)
        
        # 取消按钮
        self.cancel_button = ttk.Button(self.frame, text="取消", command=self.on_cancel)
        self.cancel_button.pack(pady=10)
        
        # 取消标志
        self.cancelled = False
        
        # 协议处理
        self.protocol("WM_DELETE_WINDOW", self.on_cancel)
    
    def update_progress(self, current: int, total: int) -> None:
        """
        更新进度
        
        Args:
            current: 当前进度值
            total: 总进度值
        """
        if total > 0:
            percentage = min(100, int((current / total) * 100))
            self.progress_var.set(percentage)
            self.progress_text.config(text=f"{percentage}%")
        else:
            self.progress_var.set(0)
            self.progress_text.config(text="0%")
        
        # 更新UI
        self.update_idletasks()
    
    def on_cancel(self) -> None:
        """取消操作"""
        self.cancelled = True
        self.destroy()


class ZipToolGUI:
    """压缩解压工具GUI类"""
    
    def __init__(self, root):
        """
        初始化GUI
        
        Args:
            root: tkinter根窗口
        """
        self.root = root
        self.root.title("ZipTool - 压缩解压工具")
        self.root.minsize(800, 600)
        
        # 设置窗口图标（如果有）
        try:
            icon_path = Utils.resource_path("icon.ico")
            if os.path.exists(icon_path):
                self.root.iconbitmap(icon_path)
        except Exception:
            pass
        
        # 创建主框架
        self.main_frame = ttk.Frame(self.root)
        self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 创建选项卡
        self.notebook = ttk.Notebook(self.main_frame)
        self.notebook.pack(fill=tk.BOTH, expand=True)
        
        # 创建压缩和解压选项卡
        self.compress_tab = ttk.Frame(self.notebook)
        self.extract_tab = ttk.Frame(self.notebook)
        
        self.notebook.add(self.compress_tab, text="压缩")
        self.notebook.add(self.extract_tab, text="解压")
        
        # 初始化压缩和解压界面
        self._init_compress_tab()
        self._init_extract_tab()
        
        # 状态栏
        self.status_bar = ttk.Label(self.root, text="就绪", relief=tk.SUNKEN, anchor=tk.W)
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 绑定关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)
        
        # 初始化拖放支持
        try:
            self.root.tk.eval('package require tkdnd')
            self._init_drag_drop()
        except tk.TclError:
            print("TkDnD package not available, drag and drop functionality disabled")
    
    def _init_compress_tab(self):
        """初始化压缩选项卡"""
        # 创建左右分栏
        left_frame = ttk.Frame(self.compress_tab)
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        right_frame = ttk.Frame(self.compress_tab)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, padx=5, pady=5)
        
        # 左侧：文件列表
        file_list_frame = ttk.LabelFrame(left_frame, text="要压缩的文件/文件夹")
        file_list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 文件列表
        self.compress_file_list = DragDropListbox(
            file_list_frame,
            selectmode=tk.EXTENDED,
            activestyle='none'
        )
        self.compress_file_list.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        # 滚动条
        scrollbar = ttk.Scrollbar(file_list_frame, orient=tk.VERTICAL, command=self.compress_file_list.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.compress_file_list.config(yscrollcommand=scrollbar.set)
        
        # 文件列表按钮
        file_list_buttons = ttk.Frame(left_frame)
        file_list_buttons.pack(fill=tk.X, pady=5)
        
        ttk.Button(file_list_buttons, text="添加文件", command=self.add_files).pack(side=tk.LEFT, padx=5)
        ttk.Button(file_list_buttons, text="添加文件夹", command=self.add_folders).pack(side=tk.LEFT, padx=5)
        ttk.Button(file_list_buttons, text="移除选中", command=self.remove_selected_files).pack(side=tk.LEFT, padx=5)
        ttk.Button(file_list_buttons, text="清空列表", command=self.clear_file_list).pack(side=tk.LEFT, padx=5)
        
        # 右侧：压缩选项
        options_frame = ttk.LabelFrame(right_frame, text="压缩选项")
        options_frame.pack(fill=tk.BOTH, padx=5, pady=5)
        
        # 压缩格式
        format_frame = ttk.Frame(options_frame)
        format_frame.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Label(format_frame, text="压缩格式:").pack(side=tk.LEFT, padx=5)
        
        # 获取支持的格式
        formats = CompressionHandler.get_supported_formats()
        format_names = list(formats.keys())
        
        self.compress_format_var = tk.StringVar(value=format_names[0])
        format_combo = ttk.Combobox(
            format_frame, 
            textvariable=self.compress_format_var,
            values=format_names,
            state="readonly",
            width=10
        )
        format_combo.pack(side=tk.LEFT, padx=5)
        
        # 输出路径
        output_frame = ttk.Frame(options_frame)
        output_frame.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Label(output_frame, text="输出路径:").pack(side=tk.LEFT, padx=5)
        
        self.output_path_var = tk.StringVar(value=Utils.get_default_output_dir())
        output_entry = ttk.Entry(output_frame, textvariable=self.output_path_var, width=30)
        output_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        
        ttk.Button(output_frame, text="浏览...", command=self.browse_output_path).pack(side=tk.LEFT, padx=5)
        
        # 输出文件名
        filename_frame = ttk.Frame(options_frame)
        filename_frame.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Label(filename_frame, text="文件名:").pack(side=tk.LEFT, padx=5)
        
        self.output_filename_var = tk.StringVar(value="archive.zip")
        filename_entry = ttk.Entry(filename_frame, textvariable=self.output_filename_var, width=30)
        filename_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        
        # 绑定格式变更事件
        format_combo.bind("<<ComboboxSelected>>", self.on_format_changed)
        
        # 压缩按钮
        compress_button = ttk.Button(
            right_frame, 
            text="开始压缩", 
            command=self.start_compression,
            style="primary.TButton"
        )
        compress_button.pack(pady=20, padx=10, fill=tk.X)
    
    def _init_extract_tab(self):
        """初始化解压选项卡"""
        # 创建左右分栏
        left_frame = ttk.Frame(self.extract_tab)
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        right_frame = ttk.Frame(self.extract_tab)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, padx=5, pady=5)
        
        # 左侧：压缩文件选择
        archive_frame = ttk.LabelFrame(left_frame, text="压缩文件")
        archive_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 文件路径
        path_frame = ttk.Frame(archive_frame)
        path_frame.pack(fill=tk.X, padx=10, pady=10)
        
        self.archive_path_var = tk.StringVar()
        archive_entry = ttk.Entry(path_frame, textvariable=self.archive_path_var, width=40)
        archive_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        
        ttk.Button(path_frame, text="浏览...", command=self.browse_archive).pack(side=tk.LEFT, padx=5)
        
        # 拖放提示
        drop_label = ttk.Label(
            archive_frame, 
            text="或将压缩文件拖放到此处",
            foreground="gray",
            anchor=tk.CENTER
        )
        drop_label.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 右侧：解压选项
        extract_options_frame = ttk.LabelFrame(right_frame, text="解压选项")
        extract_options_frame.pack(fill=tk.BOTH, padx=5, pady=5)
        
        # 输出路径
        extract_path_frame = ttk.Frame(extract_options_frame)
        extract_path_frame.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Label(extract_path_frame, text="解压到:").pack(side=tk.LEFT, padx=5)
        
        self.extract_path_var = tk.StringVar(value=Utils.get_default_output_dir())
        extract_path_entry = ttk.Entry(extract_path_frame, textvariable=self.extract_path_var, width=30)
        extract_path_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        
        ttk.Button(extract_path_frame, text="浏览...", command=self.browse_extract_path).pack(side=tk.LEFT, padx=5)
        
        # 解压按钮
        extract_button = ttk.Button(
            right_frame, 
            text="开始解压", 
            command=self.start_extraction,
            style="primary.TButton"
        )
        extract_button.pack(pady=20, padx=10, fill=tk.X)
    
    def _init_drag_drop(self):
        """初始化拖放支持"""
        # 压缩选项卡的拖放
        FileDropTarget(self.compress_file_list, self.on_files_dropped_compress)
        
        # 解压选项卡的拖放
        for widget in self.extract_tab.winfo_children():
            FileDropTarget(widget, self.on_files_dropped_extract)
    
    def on_files_dropped_compress(self, files):
        """
        处理压缩选项卡的文件拖放
        
        Args:
            files: 拖放的文件路径列表
        """
        for file_path in files:
            if file_path not in self.get_compress_file_list():
                self.compress_file_list.insert(tk.END, file_path)
        
        # 更新建议的输出文件名
        self.update_suggested_filename()
    
    def on_files_dropped_extract(self, files):
        """
        处理解压选项卡的文件拖放
        
        Args:
            files: 拖放的文件路径列表
        """
        if files and os.path.isfile(files[0]):
            self.archive_path_var.set(files[0])
    
    def add_files(self):
        """添加文件到压缩列表"""
        files = filedialog.askopenfilenames(
            title="选择要压缩的文件",
            filetypes=[("所有文件", "*.*")]
        )
        
        if files:
            current_files = self.get_compress_file_list()
            for file_path in files:
                if file_path not in current_files:
                    self.compress_file_list.insert(tk.END, file_path)
            
            # 更新建议的输出文件名
            self.update_suggested_filename()
    
    def add_folders(self):
        """添加文件夹到压缩列表"""
        folder = filedialog.askdirectory(title="选择要压缩的文件夹")
        
        if folder:
            current_files = self.get_compress_file_list()
            if folder not in current_files:
                self.compress_file_list.insert(tk.END, folder)
            
            # 更新建议的输出文件名
            self.update_suggested_filename()
    
    def remove_selected_files(self):
        """从压缩列表中移除选中的文件"""
        selected_indices = self.compress_file_list.curselection()
        
        # 从后向前删除，避免索引变化问题
        for index in sorted(selected_indices, reverse=True):
            self.compress_file_list.delete(index)
        
        # 更新建议的输出文件名
        self.update_suggested_filename()
    
    def clear_file_list(self):
        """清空压缩文件列表"""
        self.compress_file_list.delete(0, tk.END)
        
        # 重置建议的输出文件名
        self.output_filename_var.set("archive.zip")
    
    def get_compress_file_list(self):
        """
        获取压缩文件列表
        
        Returns:
            文件路径列表
        """
        return [self.compress_file_list.get(i) for i in range(self.compress_file_list.size())]
    
    def browse_output_path(self):
        """浏览输出路径"""
        folder = filedialog.askdirectory(title="选择输出目录")
        
        if folder:
            self.output_path_var.set(folder)
    
    def browse_archive(self):
        """浏览压缩文件"""
        # 获取支持的格式
        formats = CompressionHandler.get_supported_formats()
        
        # 构建文件类型过滤器
        filetypes = [("所有支持的压缩文件", " ".join(f"*{ext}" for ext in formats.values()))]
        
        # 添加各个格式的过滤器
        for format_name, ext in formats.items():
            filetypes.append((f"{format_name.upper()} 文件", f"*{ext}"))
        
        filetypes.append(("所有文件", "*.*"))
        
        # 打开文件对话框
        file_path = filedialog.askopenfilename(
            title="选择压缩文件",
            filetypes=filetypes
        )
        
        if file_path:
            self.archive_path_var.set(file_path)
    
    def browse_extract_path(self):
        """浏览解压路径"""
        folder = filedialog.askdirectory(title="选择解压目录")
        
        if folder:
            self.extract_path_var.set(folder)
    
    def on_format_changed(self, event):
        """
        处理压缩格式变更事件
        
        Args:
            event: 事件对象
        """
        # 获取当前选择的格式
        format_name = self.compress_format_var.get()
        formats = CompressionHandler.get_supported_formats()
        
        # 更新文件名扩展名
        filename = self.output_filename_var.get()
        base_name = os.path.splitext(filename)[0]
        
        # 设置新的文件名
        self.output_filename_var.set(f"{base_name}{formats[format_name]}")
    
    def update_suggested_filename(self):
        """更新建议的输出文件名"""
        files = self.get_compress_file_list()
        if not files:
            return
        
        # 获取当前选择的格式
        format_name = self.compress_format_var.get()
        formats = CompressionHandler.get_supported_formats()
        format_ext = formats[format_name]
        
        # 生成建议的文件名
        suggested_name = Utils.suggest_archive_name(files, format_ext)
        self.output_filename_var.set(suggested_name)
    
    def start_compression(self):
        """开始压缩操作"""
        # 获取文件列表
        files = self.get_compress_file_list()
        if not files:
            messagebox.showwarning("警告", "请先添加要压缩的文件或文件夹")
            return
        
        # 获取输出路径和文件名
        output_dir = self.output_path_var.get()
        output_filename = self.output_filename_var.get()
        
        # 检查输出目录是否存在
        if not Utils.is_valid_directory(output_dir):
            messagebox.showerror("错误", "输出目录不存在或无效")
            return
        
        # 构建完整输出路径
        output_path = os.path.join(output_dir, output_filename)
        
        # 检查文件是否已存在
        if os.path.exists(output_path):
            if not messagebox.askyesno("确认覆盖", f"文件 {output_filename} 已存在，是否覆盖？"):
                return
        
        # 获取压缩格式
        format_name = self.compress_format_var.get()
        
        # 创建进度对话框
        progress_dialog = ProgressDialog(self.root, "压缩中", "正在压缩文件，请稍候...")
        
        # 定义进度回调函数
        def progress_callback(current, total):
            if progress_dialog.cancelled:
                return
            
            # 在主线程中更新UI
            self.root.after(0, lambda: progress_dialog.update_progress(current, total))
        
        # 定义完成回调函数
        def on_complete(success, error_message):
            # 关闭进度对话框
            self.root.after(0, progress_dialog.destroy)
            
            if progress_dialog.cancelled:
                # 如果用户取消了操作，尝试删除未完成的文件
                try:
                    if os.path.exists(output_path):
                        os.remove(output_path)
                except Exception:
                    pass
                
                self.status_bar.config(text="操作已取消")
                return
            
            if success:
                messagebox.showinfo("完成", f"文件已成功压缩到：\n{output_path}")
                self.status_bar.config(text=f"已压缩到 {output_path}")
            else:
                messagebox.showerror("错误", f"压缩失败：{error_message}")
                self.status_bar.config(text="压缩失败")
        
        # 在单独的线程中执行压缩操作
        CompressionHandler.run_in_thread(
            CompressionHandler.compress_files,
            (files, output_path, format_name, progress_callback),
            on_complete
        )
    
    def start_extraction(self):
        """开始解压操作"""
        # 获取压缩文件路径
        archive_path = self.archive_path_var.get()
        if not archive_path or not os.path.isfile(archive_path):
            messagebox.showwarning("警告", "请选择有效的压缩文件")
            return
        
        # 获取解压路径
        extract_dir = self.extract_path_var.get()
        
        # 检查解压目录是否存在
        if not Utils.is_valid_directory(extract_dir):
            messagebox.showerror("错误", "解压目录不存在或无效")
            return
        
        # 创建进度对话框
        progress_dialog = ProgressDialog(self.root, "解压中", "正在解压文件，请稍候...")
        
        # 定义进度回调函数
        def progress_callback(current, total):
            if progress_dialog.cancelled:
                return
            
            # 在主线程中更新UI
            self.root.after(0, lambda: progress_dialog.update_progress(current, total))
        
        # 定义完成回调函数
        def on_complete(success, error_message):
            # 关闭进度对话框
            self.root.after(0, progress_dialog.destroy)
            
            if progress_dialog.cancelled:
                self.status_bar.config(text="操作已取消")
                return
            
            if success:
                messagebox.showinfo("完成", f"文件已成功解压到：\n{extract_dir}")
                self.status_bar.config(text=f"已解压到 {extract_dir}")
            else:
                messagebox.showerror("错误", f"解压失败：{error_message}")
                self.status_bar.config(text="解压失败")
        
        # 在单独的线程中执行解压操作
        CompressionHandler.run_in_thread(
            CompressionHandler.extract_archive,
            (archive_path, extract_dir, progress_callback),
            on_complete
        )
    
    def on_close(self):
        """关闭窗口处理"""
        # 清理临时文件
        try:
            Utils.clean_temp_dir()
        except Exception:
            pass
        
        # 关闭窗口
        self.root.destroy()